﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using Microsoft.DirectX.Direct3D;
using System.ComponentModel;
using System.Drawing;
using Microsoft.DirectX;

namespace DX_T1
{
    class WinForm : Form 
    {
        // bezpośredni link do urządzenia graficznego na którym wyświetlany będzie obraz
        private Device device;
        private Container components = null;
        private float angle = 0f;       // kąt rotacji
        private CustomVertex.PositionColored[] vertices;

        /* zmienne dla sfery */
        private float R_Sphere = 4;  // promienń sfery
        private int N = 30;          // ilość wierzchołków
        private int numberOfTriangles = 0;

        public float R_Torus_inside = 5;
        public float R_Torus_outside = 10;

        public WinForm() 
        {
            InitializeComponent();
            // ustawienie stylu
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
        }


        /// <summary>
        /// Inicjalizacja urządzenia
        /// </summary>
        public void InitizlizeDevice() 
        {
            PresentParameters parameters = new PresentParameters(); // parametry określające wyświetlany obraz
            parameters.Windowed = true;     // tryb okienkowy
            parameters.SwapEffect = SwapEffect.Discard; // bezpośrednie malowanie na ekranie bez przechowywania w buforach
            parameters.AutoDepthStencilFormat = DepthFormat.D16; // inicjaliazacja 16b z-bufora poprzez określenie precyzji
            parameters.EnableAutoDepthStencil = true;   // włączenie z-bufora
            device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, parameters);
                /*
                 * 0 - pierwszy graficzny adapter w komputerze
                 * 2. działanie na hardwerze
                 * 3. na tym oknie (frame'ie)
                 * 4. z wykorzystaniem programowego przetwarzania wierzchołków
                 * 5. ze zdefiniowanymi parametrami
                 */
        }

        /// <summary>
        /// Metoda odpowiedzialna za rysowanie na ekranie
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaint(PaintEventArgs e)
        {
            device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0); // wyczyszczenie ekranu na dany kolor
            device.BeginScene();    // definicja tego że zaczynamy budować scenę a więc wszystkie obiekty jakie się na niej znajdą
            device.VertexFormat = CustomVertex.PositionColored.Format;
            //device.Transform.World = Matrix.RotationY(angle);
            /*
             dodanie rotacji współrzędnych świata, obiekt będzie się kręcił wokół współrzędnej (0,0,0)
             
            device.Transform.World = Matrix.RotationZ(angle); */
            
            /*
             rotacja względem środka stworzonej figury
             */
            device.Transform.World = Matrix.Translation(5, 5, 0) * Matrix.RotationZ(angle) * Matrix.RotationX(angle);
            //device.DrawPrimitives(PrimitiveType.TriangleList, 0, numberOfTriangles);
            device.DrawUserPrimitives(PrimitiveType.TriangleList, numberOfTriangles, vertices);
            device.EndScene();      // kończenie tworzenia sceny
            device.Present();   // zaprezentowanie wyników na ekranie

            this.Invalidate();  // poprawne przemalowywanie gdy rozszerzane jest okno

            angle += 0.02f;     // zmiana kąta przy każdym przemalowaniu
        }

        #region sphere
        private void VertexDeclarationForSphere()
        {
            CustomVertex.PositionColored[,] verticesTMP = new CustomVertex.PositionColored[N, N]; //tablica wierzchołków - każdy ma pozycję i kolor

            // dodawanie współrzędnych do tymczasowej tablicy
            for (int i = 0; i < N; i++)
            {
                for (int j = 0; j < N; j++)
                {
                    float u = (float)i / (N - 1);
                    float v = (float)j / (N - 1);
                    verticesTMP[i, j].X = Set_X_Sphere(u, v);
                    verticesTMP[i, j].Y = Set_Y_Sphere(u, v) - 5.0f;
                    verticesTMP[i, j].Z = Set_Z_Sphere(u, v);
                    verticesTMP[i, j].Color = Color.Blue.ToArgb();
                }
            }

            // dodanie wierzchołków do właściwej tablicy
            List<CustomVertex.PositionColored> lista = new List<CustomVertex.PositionColored>();

            CustomVertex.PositionColored tmp = new CustomVertex.PositionColored();
            for (int i = 0; i < N; i++)
            {
                for (int j = 0; j < N; j++)
                {
                    if ((i + 1) < N && (j + 1) < N)
                    {
                        lista.Add(verticesTMP[i,j]);
                        lista.Add(verticesTMP[i + 1,j]);
                        lista.Add(verticesTMP[i + 1,j + 1]);
                        lista.Add(verticesTMP[i, j]);
                        lista.Add(verticesTMP[i, j + 1]);
                        lista.Add(verticesTMP[i + 1, j + 1]);


                        tmp = verticesTMP[i, j];
                        tmp.Z = -tmp.Z;
                        lista.Add(tmp);
                        tmp = verticesTMP[i + 1, j];
                        tmp.Z = -tmp.Z;
                        lista.Add(tmp);
                        tmp = verticesTMP[i + 1,j + 1];
                        tmp.Z = -tmp.Z;
                        lista.Add(tmp);
                        tmp = verticesTMP[i, j];
                        tmp.Z = -tmp.Z;
                        lista.Add(tmp);
                        tmp = verticesTMP[i, j + 1];
                        tmp.Z = -tmp.Z;
                        lista.Add(tmp);
                        tmp = verticesTMP[i + 1, j + 1];
                        tmp.Z = -tmp.Z;
                        lista.Add(tmp);
                        numberOfTriangles += 2;
                    }
                }
            }
            vertices = new CustomVertex.PositionColored[lista.Count];

            for (int i = 0; i < lista.Count; i++) 
            {
                vertices[i].X = lista.First().X;
                vertices[i].Y = lista.First().Y;
                vertices[i].Z = lista.First().Z;
                vertices[i].Color = lista.First().Color;
                lista.RemoveAt(0);
            }
        }
        /*SFERA*/
        //------------------------------------------------------------------------------------
        // Metody służące do wyznacznia przekształconych równań paramerycznych
        // wyznaczenie x
        public float Set_X_Sphere(float u, float v)
        {
            float x = 0;
            x = (float)(R_Sphere * Math.Cos(Math.PI * u) * Math.Cos(2 * Math.PI * v));
            return x;
        }
        //------------------------------------------------------------------------------------
        // wyznaczenie y
        public float Set_Y_Sphere(float u, float v)
        {
            float y = 0;
            y = (float)(R_Sphere * Math.Cos(Math.PI * u) * Math.Sin(2 * Math.PI * v));
            return y;
        }
        //------------------------------------------------------------------------------------
        // wyznaczenie z
        public float Set_Z_Sphere(float u, float v)
        {
            float z = 0;
            z = (float)(R_Sphere * Math.Sin(Math.PI * u));
            return z;
        }
        //------------------------------------------------------------------------------------

        #endregion

        #region torus
        private void VertexDeclarationForTorus()
        {
            CustomVertex.PositionColored[,] verticesTMP = new CustomVertex.PositionColored[N, N]; //tablica wierzchołków - każdy ma pozycję i kolor

            // dodawanie współrzędnych do tymczasowej tablicy
            for (int i = 0; i < N; i++)
            {
                for (int j = 0; j < N; j++)
                {
                    for (int k = 1; k >= 0; k--)
                    {
                        float u = (float)((i +k)% N) + 0.5f;
                        float v = (float)(j % N);
                        verticesTMP[i, j].X = Set_X_Torus(u, v);
                        verticesTMP[i, j].Y = Set_Y_Torus(u, v) - 5.0f;
                        verticesTMP[i, j].Z = Set_Z_Torus(u, v);
                        verticesTMP[i, j].Color = Color.Red.ToArgb();
                    }
                }
            }

            // dodanie wierzchołków do właściwej tablicy
            List<CustomVertex.PositionColored> lista = new List<CustomVertex.PositionColored>();

            for (int i = 0; i < N; i++)
            {
                for (int j = 0; j < N; j++)
                {
                    if ((i + 1) < N && (j + 1) < N)
                    {
                        lista.Add(verticesTMP[i, j]);
                        //lista.Add(verticesTMP[i, j]);
                        //lista.Add(verticesTMP[i + 1, j]);
                        //lista.Add(verticesTMP[i + 1, j + 1]);
                        //lista.Add(verticesTMP[i, j]);
                        //lista.Add(verticesTMP[i, j + 1]);
                        //lista.Add(verticesTMP[i + 1, j + 1]);


                        //tmp = verticesTMP[i, j];
                        //tmp.Z = -tmp.Z;
                        //lista.Add(tmp);
                        //tmp = verticesTMP[i + 1, j];
                        //tmp.Z = -tmp.Z;
                        //lista.Add(tmp);
                        //tmp = verticesTMP[i + 1, j + 1];
                        //tmp.Z = -tmp.Z;
                        //lista.Add(tmp);
                        //tmp = verticesTMP[i, j];
                        //tmp.Z = -tmp.Z;
                        //lista.Add(tmp);
                        //tmp = verticesTMP[i, j + 1];
                        //tmp.Z = -tmp.Z;
                        //lista.Add(tmp);
                        //tmp = verticesTMP[i + 1, j + 1];
                        //tmp.Z = -tmp.Z;
                        //lista.Add(tmp);
                        //numberOfTriangles += 2;
                        numberOfTriangles++;
                    }
                }
            }
            vertices = new CustomVertex.PositionColored[lista.Count];

            for (int i = 0; i < lista.Count; i++)
            {
                vertices[i].X = lista.First().X;
                vertices[i].Y = lista.First().Y;
                vertices[i].Z = lista.First().Z;
                vertices[i].Color = lista.First().Color;
                lista.RemoveAt(0);
            }
        }
        /*TORUS*/
        //------------------------------------------------------------------------------------
        // Metody służące do wyznacznia przekształconych równań paramerycznych
        // wyznaczenie x
        public float Set_X_Torus(float u, float v)
        {
            float x = 0;
            x = (float)(R_Torus_outside + R_Torus_inside * Math.Cos(2 * Math.PI * v) * Math.Cos(2 * Math.PI * u));
            return x;
        }
        //------------------------------------------------------------------------------------
        // wyznaczenie y
        public float Set_Y_Torus(float u, float v)
        {
            float y = 0;
            y = (float)(R_Torus_outside + R_Torus_inside * Math.Cos(2 * Math.PI * v) * Math.Sin(2 * Math.PI * u));
            return y;
        }
        //------------------------------------------------------------------------------------
        // wyznaczenie z
        public float Set_Z_Torus(float u, float v)
        {
            float z = 0;
            z = (float)(R_Torus_inside * Math.Sin(2 * Math.PI * v));
            return z;
        }
        //------------------------------------------------------------------------------------
        #endregion
        private void CameraPositioning()
        {

            /* określenie gdzie ma znajdować się obserwator a więc kamera
             jak ma patrzyć na wskazany obiekt, 1. parametr określa pod jakim kątem obserwator spogląda na obiekt,
             * 2. parametr określa współczynnik kształtu obrazu (obraz inny w przypadku wyświetlania na kole i w kwadracie),
             * 3. i 4. określenie zakresu widzenia (obiekty z <1 i >50 nie są wyświetlane), płaszczyzny poza którymi obiekty
             * nie będą wyświetlane
             */
            device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1f, 100f);
            /*
             pozycjonowanie kamery
             * 1. prarametr to pozycja kamery, 30 jednostek ponad punktem 0,0,0
             * 2. param - na co kamera patrzy
             * 3. param - kierunek? ustawiany przeważnie na 0,1,0
             * 
             * !!!!
             * DirectX używa lewoskrętnego układu współrzędnych więc x z prawej strony, a z wgłąb ekranu
             * 
             */
            device.Transform.View = Matrix.LookAtLH(new Vector3(0, 0, -25), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
            // ustawienie aby było światło
            device.RenderState.Lighting = false;

            /*
             dodanie światła
             * 1. typ światła
             * 2. kolor
             * 3. kierunek z którego światło pochodzi
             * 4. kierunek w którym światło się odbija
             */
            //device.Lights[0].Type = LightType.Directional;
            //device.Lights[0].Diffuse = Color.White;
            //device.Lights[0].Direction = new Vector3(0.8f, 0, -1);
            //device.Lights[0].Enabled = true;

            // wyświetli wszystkie trójkąty, nawet te które nie są przed kamerą - nie należy stosować w ostatecznym 
            // produkcie bo zwalnia proces rysowania
            device.RenderState.CullMode = Cull.None;
            // ustawienie tego że wyświetlane będą tylko krawędzie figur
            device.RenderState.FillMode = FillMode.WireFrame;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing) 
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }

        /// <summary>
        /// Metoda inicjalizująca Formsa
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.Text = "Laboratorium 1";
            this.Size = new System.Drawing.Size(500,500);
        }

        public static void Main() 
        {
            using(WinForm winForm = new WinForm())
            {
                winForm.InitizlizeDevice();
                winForm.CameraPositioning();
                winForm.VertexDeclarationForSphere();
                //winForm.VertexDeclarationForTorus();
                winForm.Show();
                Application.Run(winForm);
            }
        
        }
    }
}
